#! c:\perl\bin\perl.exe
#------------------------------------------------------
# pref.pl
# Perl script to parse the contents of prefetch files
# Based on H. Carvey's Prefetch.pl, version 0.1_20061026
# Based on H. carvey's pref.pl
# Based on Y. Khatri's prefetch.enscript, version 0.7
#
# usage: see hlpmsg()
#
# Changes:
#   20091216: add fix for bogus 'stat' when under Windows on NTFS volume
#   20091106: created
#
# copyright 2009 J-F. Gingras jean.francois.gingras@gmail.com
#------------------------------------------------------
use strict;
use ReadPrefetch;
use Math::BigInt;
use Getopt::Long;

$| = 1; # Force IO output. No buffering!

my %config;
Getopt::Long::Configure("prefix_pattern=(-|\/)");
GetOptions(\%config,qw(prefetch|p=s output|o=s fixstat|n verbose|v help|?|h));

if ($config{help} || !%config) {
  helpmsg();
}

die "No output directory entered.\n" if (! $config{output});

my $pf;
my %hdr;
my %fpb; # Filepaths Block
my %vib; # Volume Information Block
my $outdir = $config{output};
my $prefdir = $config{prefetch};
$prefdir = $ENV{'SystemRoot'}."\\Prefetch" if (! defined $prefdir);
$prefdir =~ s/\\\\/\\/g;
$prefdir =~ s/\\$//g;

die "$prefdir not found.\n" unless (-e $prefdir);

opendir(IDIR,$prefdir) || die "Could not open $prefdir: $!\n";
my @files = readdir(IDIR);
close(IDIR);
foreach my $file (@files) {
  next if ($file =~ m/\.$/ || $file =~ m/\.\.$/ || ! ($file =~ m/\.pf$/i));
  
  # get filetimes information about the prefetch file
  my ($size,$access,$mod,$creation) = (stat($prefdir."\\".$file))[7,8,9,10];
  
  $pf = undef;
  if ($pf = ReadPrefetch::new($prefdir."\\".$file)) {
    print "Prefetch Object created for file : $file\n" if ($config{verbose});
  }
  else {
    print "Prefetch Object not created.  Exiting.\n";
    next;
  }
  
  %hdr = ();
  print "Parsing header... " if ($config{verbose});
  if (%hdr = $pf->parseHeader()) {
  # no need to do anything...  
    print "OK\n" if ($config{verbose});
  }
  else {
    print "Error : ".$pf->getError()." in file ".$file."\n";
    next;
  }
  
  %fpb = ();
  print "Parsing Filepaths Block... " if ($config{verbose});
  if (%fpb = $pf->parseFilePathsBlock()) {
    print "OK\n" if ($config{verbose});
  }
  else {
    print "Error : ".$pf->getError()." in file ".$file."\n";
    next;
  }
  
  %vib = ();
  print "Parsing Volume Information Block... " if ($config{verbose});
  if (%vib = $pf->parseVolumeInformationBlock()) {
    print "OK\n" if ($config{verbose});
  }
  else {
    print "Error : ".$pf->getError()." in file ".$file."\n";
    next;
  }
  
  $pf->close();
  
  # print report
  my ($outfile) = $file =~ m/(.+)\.pf$/;
  $outfile = $outdir."\\".$outfile.".rpt";
  $outfile =~ s/\\\\/\\/g;
  open(OFILE,">",$outfile);
  print OFILE "General information :\n";
  print OFILE "  Filename  : ".$hdr{fname}."\n";
  print OFILE "  Run count : ".$hdr{runcount}."\n";
  print OFILE "  Run time  : ".gmtime(getTime($hdr{runtime_lo},$hdr{runtime_hi}))." (UTC)\n";
  print OFILE "\n";
  print OFILE "Prefetch file information:\n";
  print OFILE "  Reported size     : ".$hdr{pfsize}."\n";
  print OFILE "  Real size         : ".$size."\n";
	print OFILE "  Last access time  : ".gmtime(fixWin32StatTime($access))." (UTC)\n";
	print OFILE "  Modification time : ".gmtime(fixWin32StatTime($mod))." (UTC)\n";
	print OFILE "  Creation time     : ".gmtime(fixWin32StatTime($creation))." (UTC)\n";
  print OFILE "\n";
  print OFILE "Filepaths block (".$hdr{fpb_off}.") :\n";
  print OFILE "  ".$fpb{$_}."\n" foreach(sort keys %fpb);
  print OFILE "\n";
  print OFILE "Volume information block (".$hdr{vib_off}.") :\n";
  foreach $b (sort keys %vib) {
    print OFILE "  Volume Path   : ".$vib{$b}{path}."\n";
    print OFILE "  Serial Number : ".$vib{$b}{sn}."\n";
    print OFILE "  Creation Time : ".gmtime(getTime($vib{$b}{ctime_lo},$vib{$b}{ctime_hi}))." (UTC)\n";
    print OFILE "  ".$vib{$b}{fpb}{$_}."\n" foreach(sort keys %{ $vib{$b}{fpb} });
    print OFILE "\n";
  }
  close(OFILE);#last;
}

#---------------------------------------------------------
# getMetaData()
# get metadata from .pf files
#---------------------------------------------------------
sub getMetaData {
  my $file = $_[0];
  my $data;
  my ($runcount,$runtime);
  
  open(FH,"<",$file) || die "Could not open $file: $!\n";
  binmode(FH);
  seek(FH,0x78,0);
  read(FH,$data,8);
  my @tvals = unpack("VV",$data);
  $runtime = getTime($tvals[0],$tvals[1]);
  
  seek(FH,0x90,0);
  read(FH,$data,4);
  $runcount = unpack("V",$data);
  
  close(FH);
  return ($runcount,$runtime);
}

#---------------------------------------------------------
# fixWin32StatTime()
# Fix the date/time return by 'stat', 'lstat' and 'utime'
# under Windows because it may change by an hour as we move
# into or out of daylight saving time (DST) if the computer
# is set to "Automatically adjust clock for daylight saving
# changes" and if the file reside on a NTFS volume.
# This assume many things.
# - you are in north america
# - you are using "automatic" dayligth saving
# - you are not in a territory that use custom daylight saving
# Input : Unix-style date/time
# Output: Unix-style date/time
#
# For more information, see Win32::UTCFileTime
#---------------------------------------------------------
sub fixWin32StatTime {
  my $time = shift;
  
  return $time if (! defined $config{fixstat});
  
  my $i = 0;
  my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
                                              gmtime($time);
  $year += 1900;
  $mon  += 1;
  $wday += 1;
  if ($year >= 1987 && $year < 2007) {
    $i = 1 if ($mon == 4 && ($mday > 7 || ($wday == 1 && $hour >= 2)));
    $i = 1 if ($mon == 10 && ($mday < 25 || ($wday == 1 && $hour < 2)));
    $i = 1 if ($mon > 4 && $mon < 10);
  } elsif ($year >= 2007) {
    $i = 1 if ($mon == 3 && ($mday > 14 || ($mday >= 8 && $wday == 1 && $hour >= 2)));
    $i = 1 if ($mon == 11 && ($mday > 7 || ($wday == 1 && $hour < 2)));
    $i = 1 if ($mon > 3 && $mon < 11);
  }
  $time += ($i * 60 * 60);
}

#---------------------------------------------------------
# getTime()
# Get Unix-style date/time from FILETIME object
# Input : 8 byte FILETIME object
# Output: Unix-style date/time
# Thanks goes to Andreas Schuster for the below code, which he
# included in his ptfinder.pl
#---------------------------------------------------------
sub getTime {
  my $lo = shift;
  my $hi = shift;
  my $t;

  if ($lo == 0 && $hi == 0) {
    $t = 0;
  } else {
    $lo -= 0xd53e8000;
    $hi -= 0x019db1de;
    $t = int($hi*429.4967296 + $lo/1e7);
  };
  $t = 0 if ($t < 0);
  return $t;
}

#---------------------------------------------------------
# generateXpHash
# Compute the hash of the specified file
# Thanks goes to Yogesh Khatri for the below code, which he
# included in his EnCase Enscript.
#---------------------------------------------------------
sub generateXpHash {
  my $fullpath = shift;
  my $len  = length($fullpath)-1;
  my $hash = 0;
 
  for (0 .. $len) {
    my $num = ord(substr($fullpath,$_,1));
    if ($num > 255) {
      $hash = int(37 * ((37 * $hash) + ($num / 256)) + mod($num,256));
    } else {
      $hash = int(37 * ((37 * $hash) + $num));
    };
  }
  $hash *= 314159269;
  $hash *= -1 if ($hash < 0);
  $hash = mod($hash,1000000007);
  return $hash;
}

#---------------------------------------------------------
# generateVistaHash
# Compute the hash of the specified file
# Thanks goes to Yogesh Khatri for the below code, which he
# included in his EnCase Enscript.
#---------------------------------------------------------
sub generateVistaHash {
  my $fullpath = shift;
  my $len  = length($fullpath)-1;
  my $hash = 314159;
 
  for (0 .. $len) {
    my $num = ord(substr($fullpath,$_,1));
    if ($num > 255) {
      $hash = int(37 * ((37 * $hash) + ($num / 256)) + mod($num,256));
    } else {
      $hash = int(37 * ((37 * $hash) + $num));
    };
  }
  return $hash;
}

#---------------------------------------------------------
# helpmsg()
# Print help message and quit
#---------------------------------------------------------
sub helpmsg {
  print<< "EOT";
pref [-hv] [-p prefetch_directory] [-o output_directory]
Parse the contents of prefetch files

  -h..........................Help (print this information)
  -n..........................Use this switch if the files are on a NTFS volume
  -p prefetch_directory.......Prefetch directory to parse.
                              Default to $ENV{'SystemRoot'}\\Prefetch
  -o output_directory.........Directory that will hold the results.
  -v..........................Verbose mode.
  
Ex: C:\\>pref
    C:\\>pref Z:\\WINDOWS\\Prefetch

copyright 2009 J-F. Gingras
EOT

 exit(0);
}